package vg.modules.desktop;

import vg.interfaces.ll_services.data_base_service.data.graph.Graph;
import vg.interfaces.ll_services.data_base_service.data.header.GraphHeader;
import vg.modules.desktop.components.TabComponent;
import vg.services.graph_view_manager.interfaces.graph_view.AGraphView;
import vg.services.graph_view_manager.interfaces.tab.ATab;
import vg.services.main_manager.VGMainServiceHelper;
import vg.services.plugin_manager.event_and_request.event.*;
import vg.services.plugin_manager.event_and_request.request.*;
import vg.services.user_interface_manager.additional_swing_components.SimpleTabWithCloseButton;
import vg.services.user_interface_manager.interfaces.AUserInterfaceElement;
import vg.services.user_interface_manager.interfaces.UserInterfaceObserverAdapter;

import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

/**
 * This class realizes desktop.
 * All methods of this class by need called from EDT only!!!
 *
 * @author Timur Zolotuhin (e-mail: tzolotuhin@gmail.com)
 */

public class DesktopPanel extends AUserInterfaceElement {
	// Swing components
	private JPanel view = new JPanel(new BorderLayout());
	private JTabbedPane tabs;
	
	// Main data
	private final List<UIEventCreateNewConnection> connections;

	// Mutex
	private final Object generalMutex = new Object();
	
	/**
	 * Constructor.
	 */
	public DesktopPanel() {
		super("Desktop", null);
		
		// init main data
		this.connections = new ArrayList<UIEventCreateNewConnection>(10);
		
		// init components
		init();

        VGMainServiceHelper.userInterfaceManager.addObserver(new UserInterfaceObserverAdapter() {
            @Override
            public void dispatchEvent(AUIEvent event) {
                switch(event.getType()) {
                    case DEF_OPEN_NEW_GRAPH: {
                        UIEventOpenNewGraph bufEvent = (UIEventOpenNewGraph) event;
                        List<GraphHeader> rootGraphHeaders = VGMainServiceHelper.graphDataBaseManager.getRootGraphHeadersByGraphModelId(bufEvent.getGraphModelId());
                        Graph rootGraph = VGMainServiceHelper.graphDataBaseManager.getGraph(rootGraphHeaders.get(0).getId());
                        final AGraphView graphView = VGMainServiceHelper.graphViewManager.newGraphView(rootGraph);
                        synchronized (generalMutex) {
                            add_tab(graphView, -1);
                        }
                        break;
                    }
                    case DEF_CREATE_NEW_CONNECTION: {
                        final UIEventCreateNewConnection bufEvent = (UIEventCreateNewConnection)event;
                        synchronized (generalMutex) {
                            addNewConnection(bufEvent);
                        }
                        break;
                    }
                    case DEF_DELETE_CONNECTION: {
                        final UIEventDeleteConnection bufEvent = (UIEventDeleteConnection)event;
                        synchronized (generalMutex) {
                            removeConnection(bufEvent.getConnectId());
                        }
                        break;
                    }
                    case DEF_CHANGE_UI_STYLE: {
                        synchronized (generalMutex) {
                            updateUITheme();
                        }
                        break;
                    }
                    case DEF_RESET: {
                        synchronized (generalMutex) {
                            connections.clear();
                            tabs.removeAll();
                            tabs.updateUI();
                        }
                        break;
                    }
                    default: {
                        break;
                    }
                }
            }

            @Override
            public void dispatchRequest(AUIRequest request) {
                switch(request.getType()) {
                    case DEF_ADD_TAB: {
                        IUIRequestOwner bufOwner = request.getOwner();
                        if(bufOwner != null) {
                            bufOwner.callRequestOwner(new AUIRequest(EUIRequestType.PROCESS, null) {});
                        }

                        // execute query
                        synchronized (generalMutex) {
                            final UIRequestAddTab bufRequest = (UIRequestAddTab)request;
                            if (bufRequest.tab == null) {
                                VGMainServiceHelper.logger.printError("bufRequest.tab == null");
                                return;
                            }

                            add_tab(bufRequest.tab, -1);
                        }

                        // call request owner method (OK REQUEST)
                        if(bufOwner != null) {
                            bufOwner.callRequestOwner(new AUIRequest(EUIRequestType.OK, null) {});
                        }
                        break;
                    }
                    case DEF_CLOSE_CURRENT_TAB: {
                        IUIRequestOwner bufOwner = request.getOwner();
                        if(bufOwner != null) {
                            bufOwner.callRequestOwner(new AUIRequest(EUIRequestType.PROCESS, null) {});
                        }

                        // execute query
                        synchronized (generalMutex) {
                            int index = tabs.getSelectedIndex();
                            if(index >= 0) {
                                close_tab_by_index(index);
                            }
                        }

                        // call request owner method (OK REQUEST)
                        if(bufOwner != null) {
                            bufOwner.callRequestOwner(new AUIRequest(EUIRequestType.OK, null) {});
                        }
                        break;
                    }
                    case DEF_CLOSE_TAB_SET: {
                        // call request owner method (PROCESS REQUEST)
                        IUIRequestOwner bufOwner = request.getOwner();
                        if(bufOwner != null) {
                            bufOwner.callRequestOwner(new AUIRequest(EUIRequestType.PROCESS, null) {});
                        }

                        // execute query
                        UIRequestCloseTabSet req = (UIRequestCloseTabSet)request;
                        final Set<Integer>tabSet = req.getTabSet();
                        if(tabSet != null) {
                            synchronized (generalMutex) {
                                for(Integer buf : tabSet) {
                                    close_tab_by_id(buf);
                                }
                            }
                        }

                        // call request owner method (OK REQUEST)
                        if(bufOwner != null) {
                            bufOwner.callRequestOwner(new AUIRequest(EUIRequestType.OK, null) {});
                        }
                        break;
                    }

                    case DEF_SELECT_TAB: {
                        // call request owner method (PROCESS REQUEST)
                        IUIRequestOwner bufOwner = request.getOwner();
                        if(bufOwner != null) {
                            bufOwner.callRequestOwner(new AUIRequest(EUIRequestType.PROCESS, null) {});
                        }

                        // execute query
                        UIRequestSelectTab req = (UIRequestSelectTab)request;
                        final Integer tabId = req.getTabId();
                        synchronized (generalMutex) {
                            for(int i = 0; i < tabs.getTabCount(); i++) {
                                TabComponent tc = (TabComponent)tabs.getComponentAt(i);
                                if(tc.vg_getId() == tabId) {
                                    tabs.setSelectedIndex(i);
                                    break;
                                }
                            }
                        }

                        // call request owner method (OK REQUEST)
                        if(bufOwner != null) {
                            bufOwner.callRequestOwner(new AUIRequest(EUIRequestType.OK, null) {});
                        }
                        break;
                    }
                    case DEF_REPLACE_CURRENT_TAB: {
                        // call request owner method (PROCESS REQUEST)
                        IUIRequestOwner bufOwner = request.getOwner();
                        if(bufOwner != null) {
                            bufOwner.callRequestOwner(new AUIRequest(EUIRequestType.PROCESS, null) {});
                        }

                        // execute query
                        UIRequestReplaceCurrentTab req = (UIRequestReplaceCurrentTab)request;
                        final ATab tab = req.getTab();
                        if(tab != null) {
                            synchronized (generalMutex) {
                                replace_tab(tab.getTabTitle(), tab);
                            }
                        }

                        // call request owner method (OK REQUEST)
                        if(bufOwner != null) {
                            bufOwner.callRequestOwner(new AUIRequest(EUIRequestType.OK, null) {});
                        }
                        break;
                    }
                    default: {
                        break;
                    }
                }
            }
        });
	}

    @Override
	public JComponent getView() {
		return(this.view);
	}
	
//=============================================================================
//-----------------PRIVATE METHODS---------------------------------------------
	/**
	 * Init swing components.
	 */
	private void init() {
		this.tabs = new JTabbedPane(JTabbedPane.TOP, JTabbedPane.SCROLL_TAB_LAYOUT);
		this.view.add(this.tabs, BorderLayout.CENTER);
	    this.tabs.addChangeListener(new ChangeListener() {
	    	public void stateChanged(ChangeEvent e) {
				synchronized (generalMutex) {
					int index = tabs.getSelectedIndex();
					if(index >= 0 && index < tabs.getTabCount()) {
						TabComponent tc = (TabComponent)tabs.getComponentAt(index);
						UIEventChangeTab event = new UIEventChangeTab(tc.vg_getId(), tc.vg_getTab());
                        VGMainServiceHelper.userInterfaceManager.sendEvent(event);
					} else {
						//if all tabs close
						UIEventChangeTab event = new UIEventChangeTab(-1, null);
                        VGMainServiceHelper.userInterfaceManager.sendEvent(event);
					}

				}
			}
		});
		// ctrl-tab and ctrl-shift-tab
		KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(new KeyEventDispatcher() {					
			public boolean dispatchKeyEvent(KeyEvent e) {
				if (e.getID() == KeyEvent.KEY_PRESSED &&  e.getKeyCode() == KeyEvent.VK_TAB && e.isControlDown()) {
					int tabcount = tabs.getTabCount();
					if (tabcount > 0) {
						int sel = tabs.getSelectedIndex();
						if (e.isShiftDown()) {
							sel = sel - 1;
							if (sel < 0)
								sel = tabcount - 1;
						} else {
							sel = sel + 1;
							if (sel >= tabcount)
								sel = 0;
						}
						tabs.setSelectedIndex(sel);
					}
					return(true);
				}
				return(false);
			}
		});
	}
	/**
	 * This method adds new connection to each graph view.
	 */
	private void addNewConnection(UIEventCreateNewConnection connection) {
		for(int i = 0; i < this.tabs.getTabCount(); i++) {
			TabComponent tc = (TabComponent)this.tabs.getComponentAt(i);
			ATab tab = tc.vg_getTab();
			if (tab != null && tab instanceof AGraphView) {
				AGraphView igv = (AGraphView)tab;
				// TODO
				//igv.addNewConnection(connection.getConnectionId(), connection.getConnectionName(), connection.getAnchor(), connection.getGraphModelId());
			}
		}
		this.connections.add(connection);
	}

	/**
	 * This method removes exist connection from each graph view.
	 */
	private void removeConnection(int connectionId) {
		for(UIEventCreateNewConnection buf : this.connections) {
			if(buf.getConnectionId() == connectionId) {
				this.connections.remove(buf);
				for(int i = 0; i < this.tabs.getTabCount(); i++) {
					TabComponent tc = (TabComponent)this.tabs.getComponentAt(i);
					ATab tab = tc.vg_getTab();
					if (tab != null && tab instanceof AGraphView) {
						AGraphView igv = (AGraphView)tab;
						// TODO
						//igv.removeConnection(connectionId);
					}
				}
			}
		}
	}
	
	/**
	 * This method adds new tab on the desktop.
	 * @param new_tab - new tab, which we want open in new tab.
	 * This method by need called from EDT only!!!
	 */
	private void add_tab(ATab new_tab, int index) {
		if(new_tab == null) {
            VGMainServiceHelper.logger.printError("[" + this.getClass().getName() + ".add_tab] GraphView = null. Can't create new tab for empty ATab view.");
		}
		
		// Wrapper for ATab
		TabComponent new_tab_component = new TabComponent(new_tab);
		String new_tab_title = new_tab_component.vg_getTabTitle();
		
		boolean[] array = new boolean[this.tabs.getTabCount() + 2]; // +2: if rename and for next title
		for(int i = 0; i < this.tabs.getTabCount(); i++) {
			TabComponent next_tc = (TabComponent)this.tabs.getComponentAt(i);
			String next_tc_title = next_tc.vg_getTabTitle();
			
			if(next_tc_title != null && new_tab_title != null && next_tc_title.equals(new_tab_title)) {
				if(next_tc.vg_getNumber() >= array.length) {
					array[array.length - 1] = true;
				} else {
					array[next_tc.vg_getNumber()] = true;
				}
				array[0] = true; // otherwise the TabPane will contains a tab with index 0
				if(next_tc.vg_getNumber() == 0) {
					next_tc.vg_setNumber(1);
					tabs.setTitleAt(i, next_tc.vg_getFullTitle());
					array[1] = true;
					UIEventRenameTab renameEvent = new UIEventRenameTab(next_tc.vg_getId(), next_tc.vg_getFullTitle());
                    VGMainServiceHelper.userInterfaceManager.sendEvent(renameEvent);
				}
			}
		}
		
		// set numbers to tabs
		for(int i = 0; i < array.length; i++) {
			if(array[i] == false) {
				new_tab_component.vg_setNumber(i);
				break;
			}
		}
		
		// send event about creating new tab
		UIEventOpenNewTab event = new UIEventOpenNewTab(new_tab_component.vg_getId(), new_tab_component.vg_getTab());
        VGMainServiceHelper.userInterfaceManager.sendEvent(event);
		if(index < 0) {
			tabs.addTab(new_tab_component.vg_getFullTitle(), new_tab_component);
			index = tabs.getTabCount() - 1;
		} else {
			tabs.insertTab(new_tab_component.vg_getFullTitle(), null, new_tab_component, null, index);
		}
		
		// add close button for new tab
		tabs.setTabComponentAt(index, new SimpleTabWithCloseButton(tabs, new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				synchronized (generalMutex) {
					int index = tabs.indexOfTabComponent((SimpleTabWithCloseButton)e.getSource());
					close_tab_by_index(index);
				}
			}
		}));
		tabs.setSelectedIndex(index);
	}
	
	/**
	 * This method replaces current tab on the desktop.
	 * @param title - title of the tab.
	 * @param tab - tab, which we want open in new tab.
	 * This method by need called from EDT only!!!
	 */
	private void replace_tab(String title, ATab tab) {
		int index = tabs.getSelectedIndex(); // get current tab
		close_tab_by_index(index);
		add_tab(tab, index);
	}
	
	/**
	 * This method closes tab by id.
	 * @param id - id of tab.
	 * This method by need called from EDT only!!!
	 */
	private void close_tab_by_id(int id) {
		for(int i = 0; i < this.tabs.getTabCount(); i++) {
			TabComponent tc = (TabComponent)this.tabs.getComponentAt(i);
			if(tc.vg_getId() == id) {
				this.tabs.removeTabAt(i);
				UIEventCloseTab event = new UIEventCloseTab(id);
                VGMainServiceHelper.userInterfaceManager.sendEvent(event);
			}
		}
	}
	/**
	 * This method closes tab by index.
	 * @param index - index of tab in tab pane.
	 * This method by need called from EDT only!!!
	 */
	private void close_tab_by_index(int index) {
		TabComponent tc = (TabComponent)tabs.getComponentAt(index);
		tabs.removeTabAt(index);
		UIEventCloseTab event = new UIEventCloseTab(tc.vg_getId());
        VGMainServiceHelper.userInterfaceManager.sendEvent(event);
	}
	
	/**
	 * This method update ui theme.
	 * This method by need called from EDT only!!!
	 */
	private void updateUITheme() {
		SwingUtilities.updateComponentTreeUI(view);
	}
}
